<template autoRoute="true">
  <div class="editor-main" v-loading="loading">
    <div class="top">
      <div
        @click="goBack"
        style="
          color: #5e5e5e;
          cursor: pointer;
          display: flex;
          align-items: center;
          margin-right: 8px;
        "
      >
        <img style="display: block" src="@/assets/back.svg" alt="" />
        <span style="font-size: 14px">返回</span>
      </div>
      <div style="margin-right: auto; font-weight: 500; font-size: 16px">
        {{ titleMap?.[route?.params?.pageKey as any] }}
      </div>
      <el-button size="small" @click="clearLayout" type="default" text>
        <img
          @click="goBack"
          style="display: block; margin-right: 5px; cursor: pointer"
          src="@/assets/clear.svg"
          alt=""
        />
        清空</el-button
      >
      <el-button size="small" @click="goBack" type="default" text
        >取消</el-button
      >
      <el-button size="small" @click="add" type="primary" text
        >保存布局</el-button
      >
    </div>
    <div class="container-add" @click.stop="layoutClick">
      <div class="container-left">
        <div
          v-for="item in componentList"
          class="droppable-element"
          draggable="true"
          unselectable="on"
          @drag.prevent="drag(item)"
          @dragend.prevent="dragEnd(item)"
          :key="item.type"
        >
          <div style="color: #141414">{{ item.value }}</div>

          <img
            v-if="!hasSelectCom.includes(item.type)"
            style="display: block"
            src="@/assets/add.svg"
            alt=""
            @click.stop="handleAdd(item)"
          />

          <img
            v-else
            style="display: block; user-select: none"
            src="@/assets/close.svg"
            alt=""
            @click.stop="handleClose(item)"
          />
        </div>
      </div>
      <div ref="wrapper" class="container-right">
        <GridLayout
          ref="gridLayout"
          v-model:layout="layout"
          :row-height="30"
          :col-num="24"
          :class="{
            line: showGrid,
          }"
        >
          <GridItem
            v-for="item in layout"
            :key="item.i"
            :x="item.x"
            :y="item.y"
            :w="item.w"
            :h="item.h"
            :i="item.i"
            :minW="item.minW ?? 4"
            :minH="item.minH ?? 4"
            drag-allow-from=".vue-draggable-handle"
            drag-ignore-from=".no-drag"
            @mouseover.stop="handleMouseOver(item)"
            @mouseout.stop="handleMouseOut"
            @click.stop="handleClick(item)"
            :class="{
              active: Object.is(item.i, activeId),
            }"
            @move="move"
            @moved="moved"
            @resize="resize"
            @resized="resized"
          >
            <div class="gird_item_container">
              <!-- 拖拽区 -->
              <div
                :style="{
                  visibility: [mouseId, activeId].includes(item.i)
                    ? 'visible'
                    : 'hidden',
                }"
                class="vue-draggable-handle"
              >
                <img style="display: block" src="@/assets/move.svg" alt="" />
              </div>
              <!-- 编辑器 -->
              <div class="no-drag">
                <component
                  style="overflow: auto"
                  :is="item.type"
                  :item="item"
                  :key="item.data"
                >
                  <template #default>
                    <div
                      style="
                        color: black;
                        overflow: hidden;
                        text-overflow: ellipsis;
                        white-space: nowrap;
                      "
                    >
                      {{ (dragSourceMap as any)[item.type] }}
                    </div>
                  </template>
                  <template #op>
                    <div
                      :style="{
                        visibility: [mouseId, activeId].includes(item.i)
                          ? 'visible'
                          : 'hidden',
                      }"
                    >
                      <el-popover placement="bottom" trigger="click">
                        <template #reference>
                          <img
                            style="display: block; cursor: pointer"
                            src="@/assets/more.svg"
                            alt=""
                          />
                        </template>
                        <div style="cursor: pointer; width: 60px">
                          <div
                            class="more-config"
                            @click.stop="deleteCom(item)"
                          >
                            <img
                              style="
                                display: block;
                                margin-right: 5px;
                                cursor: pointer;
                              "
                              src="@/assets/delete.svg"
                              alt=""
                            />
                            <div>删除</div>
                          </div>
                        </div>
                      </el-popover>
                    </div>
                  </template>
                </component>
              </div>
            </div>
          </GridItem>
        </GridLayout>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
  /**
   * @description 编辑器
   */
  import { onBeforeUnmount, onMounted, ref, nextTick, computed } from "vue";
  import { GridLayout, GridItem } from "grid-layout-plus";
  import { ElMessage } from "element-plus";
  import { titleMap } from "./const.ts";

  import {
    getProjectOverview,
    saveProjectConfig,
    getProjectConfig,
    getProjectConfigById,
  } from "./api.ts";
  import { useRouter, useRoute } from "vue-router";
  import { Customcomponents, componentList, dragSourceMap } from "./const.ts";

  const router = useRouter();
  const route = useRoute();
  let hasSelectCom = computed(() => {
    return layout.value.map((ele) => ele.type) ?? [];
  });
  defineOptions({
    components: Customcomponents,
  });

  const resultData = ref({}); // 存储组件数据
  const showGrid = ref(false);

  const layout = ref<any[]>([]);
  const mouseId = ref("");
  const activeId = ref("");
  const loading = ref<boolean>(false);
  const wrapper = ref<HTMLElement>();
  const gridLayout = ref();

  const mouseAt = { x: -1, y: -1 };
  const defaultW = 12;
  const defaultH = 8;

  function syncMousePosition(event: MouseEvent) {
    mouseAt.x = event.clientX;
    mouseAt.y = event.clientY;
  }
  const dropId = "";
  const dragItem = { x: -1, y: -1, w: defaultW, h: defaultH, i: "" };

  /**
   * @description 处理拖拽区新增
   */
  const handleAdd = (node: any) => {
    layout.value.push({
      x: 0,
      y: 0,
      w: defaultW,
      h: defaultH,
      i: layout.value.length,
      moved: false,
      type: node.type,
      data: (dataMap(resultData.value) as any)?.[node.type] ?? [],
    });
  };

  /**
   * @description 处理拖拽区删除
   */
  const handleClose = (node: any) => {
    const list = layout.value.filter((ele) => !Object.is(ele.type, node.type));
    layout.value = list ?? [];
  };

  /**
   * @description 处理拖拽进行中
   */
  const drag = (node: any) => {
    // if (hasSelectCom.value.includes(node.type)) {
    //   return;
    // }
    const parentRect = wrapper.value?.getBoundingClientRect();
    if (!parentRect || !gridLayout.value) return;
    const mouseInGrid =
      mouseAt.x > parentRect.left &&
      mouseAt.x < parentRect.right &&
      mouseAt.y > parentRect.top &&
      mouseAt.y < parentRect.bottom;
    if (mouseInGrid && !layout.value.find((item) => item.i === dropId)) {
      layout.value.push({
        x: (layout.value.length * defaultW) % 24,
        y: layout.value.length + 24, // puts it at the bottom
        w: defaultW,
        h: defaultH,
        i: dropId,
        type: node.type,
        data: (dataMap(resultData.value) as any)?.[node.type] ?? [],
      });
      showGrid.value = true;
    }
    const index = layout.value.findIndex((item) => item.i === dropId);
    if (index !== -1) {
      const item = gridLayout.value.getItem(dropId);
      if (!item) return;
      try {
        item.wrapper.style.display = "none";
      } catch (e) {}

      Object.assign(item.state, {
        top: mouseAt.y - parentRect.top,
        left: mouseAt.x - parentRect.left,
      });
      const newPos = item.calcXY(
        mouseAt.y - parentRect.top,
        mouseAt.x - parentRect.left,
      );

      if (mouseInGrid) {
        gridLayout.value.dragEvent(
          "dragstart",
          dropId,
          newPos.x,
          newPos.y,
          dragItem.h,
          dragItem.w,
        );
        dragItem.i = String(index);
        dragItem.x = layout.value[index].x;
        dragItem.y = layout.value[index].y;
      } else {
        gridLayout.value.dragEvent(
          "dragend",
          dropId,
          newPos.x,
          newPos.y,
          dragItem.h,
          dragItem.w,
        );
        layout.value = layout.value.filter((item) => item.i !== dropId);
      }
    }
  };
  /**
   * @description 处理拖拽结束
   */
  const dragEnd = async (node: any) => {
    await nextTick();
    showGrid.value = false;
    const parentRect = wrapper.value?.getBoundingClientRect();
    if (!parentRect || !gridLayout.value) return;
    const mouseInGrid =
      mouseAt.x > parentRect.left &&
      mouseAt.x < parentRect.right &&
      mouseAt.y > parentRect.top &&
      mouseAt.y < parentRect.bottom;
    if (mouseInGrid) {
      gridLayout.value.dragEvent(
        "dragend",
        dropId,
        dragItem.x,
        dragItem.y,
        dragItem.h,
        dragItem.w,
      );
      layout.value = layout.value.filter((item) => item.i !== dropId);
    } else {
      return;
    }

    // 确保只能拖进相同一个组件
    if (!hasSelectCom.value.includes(node.type)) {
      layout.value.push({
        x: dragItem.x,
        y: dragItem.y,
        w: dragItem.w,
        h: dragItem.h,
        i: dragItem.i,
        type: node.type,
        data: (dataMap(resultData.value) as any)?.[node.type] ?? [],
      });
      gridLayout.value.dragEvent(
        "dragend",
        dragItem.i,
        dragItem.x,
        dragItem.y,
        dragItem.h,
        dragItem.w,
      );

      const item = gridLayout.value.getItem(dropId);

      if (!item) return;
      try {
        item.wrapper.style.display = "";
      } catch (e) {}
    }
  };

  /**
   * @description 清空画布
   */
  const clearLayout = () => {
    layout.value = [];
  };

  /**
   * @description 处理组件鼠标移入事件
   */
  const handleMouseOver = (item: any) => {
    mouseId.value = item.i;
  };

  /**
   * @description 处理组件鼠标移出事件
   */
  const handleMouseOut = () => {
    mouseId.value = "";
  };
  /**
   * @description 处理组件点击事件
   */
  const handleClick = (item: any) => {
    activeId.value = item.i;
  };

  /**
   * @description 路由回退
   */
  const goBack = () => {
    router.back();
  };

  /**
   * @description 外侧点击
   */
  const layoutClick = () => {
    activeId.value = "";
    mouseId.value = "";
  };

  /**
   * @description 处理删除组件逻辑
   */

  const deleteCom = (item: any) => {
    const index = layout.value.findIndex(
      (ele) =>
        Object.is(ele.i, item.i) &&
        Object.is(ele.y, item.y) &&
        Object.is(ele.x, item.x),
    );
    if (index > -1) {
      layout.value.splice(index, 1);
    }
  };

  /**
   * @description 处理从detail获取的数据
   */
  const dataMap = (data: any) => {
    const {
      plannedStartTime,
      actualStartTime,
      progress,
      remark,
      creatorObject,
      createTime,
      creatorName,
      creator,
      name,
      icon,
      projectMilestoneVoList,
      projectMemberVOList,
      projectAnnouncementVoList,
      projectUpdatesVoList,
    } = data ?? {};
    return {
      ProjectDetail: {
        plannedStartTime,
        actualStartTime,
        progress,
        remark,
        creatorObject,
        createTime,
        creatorName,
        creator,
        name,
        icon,
      },
      Announcement: { projectAnnouncementVoList },
      Milestone: { projectMilestoneVoList },
      ProjectTeamMember: {
        projectMemberVOList,
      },
      ProjectDynamic: {
        projectUpdatesVoList,
      },
    };
  };

  /**
   * @description 处理项目保存逻辑
   */
  const add = async () => {
    try {
      const params = {
        pageKey: route?.params?.pageKey,
        pageValue: JSON.stringify(
          layout.value?.map((e) => ({ ...e, data: undefined })),
        ),
        projectId: route?.params?.projectId || undefined, //全局不用projectId
      };
      await saveProjectConfig(params);
      ElMessage.success("保存布局成功！");
      router.back();
    } catch (error) {
      ElMessage.error("保存布局失败！");
    }
  };

  /**
   * @description 获取布局 通过key 获取
   */
  const getLayoutByKey = async () => {
    try {
      const res = await getProjectConfig(route?.params?.pageKey as any);
      const pageValue = JSON.parse(res?.pageValue ?? "[]");
      setLayoutData(pageValue);
    } catch (error) {
      ElMessage.error("获取布局数据失败！");
    }
  };

  /**
   * @description 获取布局 通过projectId 获取
   */
  const getLayoutByProjectId = async () => {
    try {
      const projectId = route?.params?.projectId;
      const res = await getProjectConfigById(projectId as string);
      const pageValue = JSON.parse(res?.pageValue ?? "[]");
      setLayoutData(pageValue);
    } catch (error) {
      ElMessage.error("获取布局数据失败！");
    }
  };

  /**
   * @description 给布局添加数据
   */
  const setLayoutData = async (configData: any) => {
    try {
      const result = await getProjectOverview(route.params.id as any);
      resultData.value = result;
      layout.value = configData?.map((ele: any) => ({
        ...ele,
        data: (dataMap(result) as any)?.[ele.type] ?? [],
      }));
    } catch (error) {
      ElMessage.error("获取数据失败！");
    } finally {
      loading.value = false;
    }
  };
  /**
   * @description 对组件移动的监听
   */
  const move = () => {
    showGrid.value = true;
  };

  const moved = async () => {
    await nextTick();
    showGrid.value = false;
  };

  const resize = () => {
    showGrid.value = true;
  };

  const resized = async () => {
    await nextTick();
    showGrid.value = false;
  };

  onMounted(async () => {
    document.addEventListener("dragover", syncMousePosition);
    if (["edit", "browse"].includes(route.params.state as string)) {
      if (route?.params?.projectId) {
        loading.value = true;
        getLayoutByProjectId();
        return;
      }
      if (Object.is("globalShapeConfig", route?.params?.pageKey)) {
        loading.value = true;
        getLayoutByKey();
      }
    }
  });

  onBeforeUnmount(() => {
    document.removeEventListener("dragover", syncMousePosition);
  });
</script>

<style scoped lang="scss">
  $editor-radius: 2px;
  $editor-border-width: 1px;
  $editor-hover-color: rgba(26, 161, 251, 0.4);
  $editor-active-color: #1aa1fb;
  .editor-main {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    height: 100vh;
    overflow: hidden;
    .top {
      padding: 5px;
      border-bottom: 1px solid #e6e6e6;
      display: flex;
      justify-content: flex-start;
      align-items: center;
    }
    .container-add {
      flex: 1;
      display: flex;
      flex-direction: row;
      justify-content: flex-start;
      .container-left {
        box-sizing: border-box;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: flex-start;
        width: 150px;
        padding: 0 10px;
        height: calc(100vh - 40px);
        overflow-y: auto;
        overflow-x: hidden;
        background-color: #fff;
        .droppable-element {
          border-radius: 4px;
          margin-bottom: 5px;
          cursor: pointer;
          padding: 5px;
          width: 100%;
          display: flex;
          flex-direction: row;
          align-items: center;
          justify-content: space-between;
          font-size: 14px;
          &:hover {
            background-color: #f8f8f8;
          }
        }
      }

      .container-right {
        flex: 1;
        overflow: auto;
        position: relative;
        height: 100vh;
        background-color: #f2f3f5;
        .vgl-layout {
          --vgl-placeholder-bg: gray;
          background-color: #f2f3f5;
          margin-bottom: 35px;
        }
      }

      .gird_item_container {
        position: absolute;
        width: 100%;
        height: 100%;
        display: flex;
        flex-direction: column;
        height: 100%;
        margin: 0px 20px 0px 0px;
        .vue-draggable-handle {
          width: 100%;
          display: flex;
          flex-direction: row;
          align-items: center;
          justify-content: center;
        }

        .no-drag {
          flex: 1;
        }
      }
    }
  }

  .more-config {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: flex-start;
  }

  :deep(.vgl-item--placeholder) {
    border-radius: $editor-radius;
  }
  .line {
    // background-color: #fff;
    // user-select: none;
    // height: 100% !important;
    // &::before {
    //   position: absolute;
    //   width: calc(100% - 5px);
    //   height: calc(100% - 5px);
    //   margin: 5px;
    //   content: "";
    //   background-image:
    //     linear-gradient(to right, lightgrey 1px, transparent 1px),
    //     linear-gradient(to bottom, lightgrey 1px, transparent 1px);
    //   background-repeat: repeat;
    //   background-size: calc(calc(100% - 5px) / 24) 40px;
    // }
  }

  :deep(.vgl-item:not(.vgl-item--placeholder)) {
    background-color: #fff;
    border: $editor-border-width solid #e6e6e6;
    border-radius: $editor-radius;
    &:before {
      display: none;
    }
    &:hover {
      border: $editor-border-width solid $editor-hover-color;
      &:before {
        display: block;
        content: "";
        position: absolute;
        bottom: -4px;
        right: -4px;
        height: 8px;
        width: 8px;
        border-radius: 50%;
        background: $editor-active-color;
        border: $editor-border-width solid #fff;
      }
    }
  }

  .active {
    border: $editor-border-width solid $editor-active-color !important;
  }

  :deep(.vgl-item--resizing) {
    opacity: 90%;
  }
  :deep(.vgl-item__resizer:before) {
    display: none;
  }
</style>
